/*
 * MIT License
 *
 * Copyright (c) 2019-2021 JetBrains s.r.o.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.jetbrains.projector.util.logging

import java.io.IOException
import java.io.PrintStream
import java.io.PrintWriter
import java.io.StringWriter

@Suppress("FunctionName")  // to make the function look like class, its name starts with a capital
public actual fun Logger(tag: String): Logger = loggerFactory(tag)

public var loggerFactory: (tag: String) -> Logger = { ConsoleJvmLogger(it) }  // todo: maybe use DI here?

public class ConsoleJvmLogger(private val tag: String) : Logger {

  public override fun error(t: Throwable?, lazyMessage: () -> String): Unit = log(System.err, LogLevel.ERROR, tag, t, lazyMessage)
  public override fun info(t: Throwable?, lazyMessage: () -> String): Unit = log(System.out, LogLevel.INFO, tag, t, lazyMessage)
  public override fun debug(t: Throwable?, lazyMessage: () -> String): Unit = log(System.out, LogLevel.DEBUG, tag, t, lazyMessage)

  private enum class LogLevel(val prefix: String) {

    ERROR("ERROR"),
    INFO("INFO"),
    DEBUG("DEBUG"),
  }

  private companion object {

    private fun log(stream: PrintStream, level: LogLevel, tag: String, t: Throwable?, lazyMessage: () -> String) {
      stream.println("[${level.prefix}] :: $tag :: ${lazyMessage()}${t.suffixForLog}")
    }

    private val Throwable?.suffixForLog: String
      get() = when (this) {
        null -> ""

        else -> " :: $stackTraceString"
      }

    private val Throwable.stackTraceString: String
      get() {
        val str = StringWriter()
        val writer = PrintWriter(str)

        try {
          this.printStackTrace(writer)
          return str.buffer.toString()
        }
        finally {
          try {
            str.close()
            writer.close()
          }
          catch (e: IOException) {
          }
        }
      }
  }
}
